﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using System.Configuration;
using Geleximco.Common;

namespace Geleximco.Presentation {
    [ToolboxData("<{0}:CustomGridView runat=server></{0}:CustomGridView>")]
    public class CustomGridView : GridView {
        #region Constants

        private const char SORT_COLUMN_AND_DIRECTION_SEPARATOR = ' ';
        private const char SORT_COLUMN_SEPARATOR = ',';
        private const string DESCENDING_SORT_DIRECTION = "DESC";
        private const string ASCENDING_SORT_DIRECTION = "ASC";
        private const int MAX_NBR_OF_COLUMNS_IN_SORT_EXP = 5;

        private const int PREVIOUS_PAGE_SET = -1;
        private const int NEXT_PAGE_SET = -2;
        private const string CSS_PAGER_TABLE = "pager_style";
        private const string CSS_PAGER_CELL = "pager_cell";
        private const string URL_NEXT_PAGE_IMG = "~/images/next_page.gif";
        private const string URL_PREV_PAGE_IMG = "~/images/prev_page.gif";
        private const string URL_NEXT_PAGE_SET_IMG = "~/images/next_page_set.gif";
        private const string URL_PREV_PAGE_SET_IMG = "~/images/prev_page_set.gif";
        private const string DEFAULT_NBR_PAGES_PER_SET = "NbrPagesPerSet";

        #endregion Constants
        #region IFields

        private string m_currentSortExp = string.Empty;
        private string m_ascImageUrl;
        private string m_descImageUrl;
        private bool m_alwayShowHeader;
        private int m_defaultSortColumnIndex = 0;
        private bool m_visibleSortImageInFirstTime = true;
        // For search and sort, we declare the variable for full selected index
        // when sorting, the full selected index are assigned.
        // when paging, the selected index in grid view are calculate base the full selected index
        private int m_fullSelectedIndex = -1;

        #endregion IFields
        #region IProperties

        [Browsable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        public string AscImageURL {
            get {
                if (String.IsNullOrEmpty(m_ascImageUrl)) {
                    m_ascImageUrl = WebConstants.SORT_ASC_IMG_URL;
                }
                return m_ascImageUrl;
            }
            set {
                m_ascImageUrl = value;
            }
        }

        [Browsable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        public string DescImageURL {
            get {
                if (String.IsNullOrEmpty(m_descImageUrl)) {
                    m_descImageUrl = WebConstants.SORT_DESC_IMG_URL;
                }
                return m_descImageUrl;
            }
            set {
                m_descImageUrl = value;
            }
        }

        [Browsable(true)]
        [Category("Appearance")]
        [DefaultValue(10)]
        public int NumPagesPerSet {
            get {
                return PagerSettings.PageButtonCount;
            }
            set {
                PagerSettings.PageButtonCount = value;
            }
        }

        [Browsable(true)]
        [Category("Appearance")]
        [DefaultValue(false)]
        public bool AlwayShowHeader {
            get {
                return m_alwayShowHeader;
            }
            set {
                m_alwayShowHeader = value;
            }
        }

        [Browsable(true)]
        [Category("Appearance")]
        [DefaultValue(true)]
        public bool VisbleSortImageInFirstTime {
            get {
                return m_visibleSortImageInFirstTime;
            }
            set {
                m_visibleSortImageInFirstTime = value;
            }
        }

        /// <summary>
        ///  Default sort expression for gridview.
        /// </summary>
        public string DefaultSortExpression {
            set {
                m_currentSortExp = value;
            }
            get {
                return m_currentSortExp;
            }
        }

        /// <summary>
        /// Index of the column used to sort the grid view at the first time
        /// (after searching from database).
        /// </summary>
        public int DefaultSortColumnIndex {
            set {
                m_currentSortExp = string.Empty;
                m_defaultSortColumnIndex = value;
            }
            get {
                return m_defaultSortColumnIndex;
            }
        }

        /// <summary>
        /// Calculate the number of sets of pages.
        /// </summary>
        private int SetCount {
            get {
                int numberOfSet = (int)(PageCount / NumPagesPerSet);
                if (PageCount % NumPagesPerSet > 0) {
                    numberOfSet++;
                }
                return numberOfSet;
            }
        }

        /// <summary>
        /// Calculate the index of page set.
        /// </summary>
        private int PageSetIndex {
            get {
                return (int)(PageIndex / NumPagesPerSet);
            }
        }

        #endregion IProperties
        #region IMethods
        #region Misc

        //Store the sort expression in control 
        //state in order to display the sort icon
        protected override void OnInit(EventArgs e) {
            Page.RegisterRequiresControlState(this);
            this.Sorting += new GridViewSortEventHandler(OnCustomGridViewSorting);
            this.PageIndexChanging += new GridViewPageEventHandler(OnCustomGridViewPageIndexChanging);
            NumPagesPerSet = int.Parse(ConfigurationManager.AppSettings[DEFAULT_NBR_PAGES_PER_SET]);
            base.OnInit(e);
        }

        protected override void LoadControlState(object savedState) {
            object[] rgState = (object[])savedState;
            base.LoadControlState(rgState[0]);
            m_currentSortExp = (string)rgState[1];
            m_ascImageUrl = (string)rgState[2];
            m_descImageUrl = (string)rgState[3];
            m_visibleSortImageInFirstTime = (bool)rgState[4];
            m_fullSelectedIndex = (int)rgState[5];
            m_defaultSortColumnIndex = (int)rgState[6];
            m_alwayShowHeader = (bool)rgState[7];

        }

        protected override object SaveControlState() {
            object[] rgState = new object[8];
            rgState[0] = base.SaveControlState();
            rgState[1] = GetSortExpression();
            rgState[2] = m_ascImageUrl;
            rgState[3] = m_descImageUrl;
            rgState[4] = m_visibleSortImageInFirstTime;
            rgState[5] = m_fullSelectedIndex;
            rgState[6] = m_defaultSortColumnIndex;
            rgState[7] = m_alwayShowHeader;
            return rgState;
        }

        /// <summary>
        /// Override this method to display header row when datasource is emtpy
        /// </summary>
        /// <param name="dataSource"></param>
        /// <param name="dataBinding"></param>
        /// <returns></returns>
        protected override int CreateChildControls(
                System.Collections.IEnumerable dataSource, bool dataBinding) {
            int numRows = base.CreateChildControls(dataSource, dataBinding);
            //no data rows created, create empty table if enabled
            if (numRows == 0 && AlwayShowHeader) {
                //create table
                Table table = new Table();
                table.ID = this.ID;

                //create a new header row
                GridViewRow row = base.CreateRow(-1, -1,
                                                 DataControlRowType.Header,
                                                 DataControlRowState.Normal);

                //convert the exisiting columns into an array and initialize
                DataControlField[] fields =
                    new DataControlField[this.Columns.Count];
                this.Columns.CopyTo(fields, 0);
                this.InitializeRow(row, fields);

                //fix width of Header row if there are no data
                if (Columns.Count == row.Cells.Count) {
                    for (int i = 0; i < Columns.Count; i++) {
                        row.Cells[i].Width = Columns[i].ItemStyle.Width;
                    }
                }
                //Skip data change when click header row
                SkipDataModificiationCheck(row);
                table.Rows.Add(row);

                if (this.ShowFooter) {
                    //create footer row
                    GridViewRow _footerRow2 = base.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal);

                    this.InitializeRow(_footerRow2, fields);
                    table.Rows.Add(_footerRow2);
                }


                this.Controls.Add(table);
            }
            return numRows;
        }

        protected override void OnRowCreated(GridViewRowEventArgs e) {
            base.OnRowCreated(e);
            if (String.IsNullOrEmpty(m_currentSortExp)) {
                m_currentSortExp = Columns[m_defaultSortColumnIndex].SortExpression +
                    SORT_COLUMN_AND_DIRECTION_SEPARATOR + ASCENDING_SORT_DIRECTION;
            }
            if (e.Row.RowType == DataControlRowType.Header) {
                SkipDataModificiationCheck(e.Row);
                if (AllowSorting) {
                    AddSortImage(e.Row);
                }

                foreach (TableCell cell in e.Row.Cells) {
                    cell.CssClass = "head_cell";
                }
            } else if (e.Row.RowType == DataControlRowType.Pager) {
                e.Row.Attributes.Add("align", "center");
                HighLightCurrentPage(e.Row);
                AddBrackets(e.Row);
                AddNextPrevButtons(e.Row);
                AddPageSetNextPrevButtons(e.Row);
                SkipDataModificiationCheck(e.Row);
            } else if (e.Row.RowType == DataControlRowType.DataRow) {
                foreach (TableCell cell in e.Row.Cells) {
                    cell.CssClass = "grid_cell";
                }
            }

            foreach (TableCell cell in e.Row.Cells) {
                foreach (Control ctl in cell.Controls) {
                    if (ctl is WebControl && !(ctl is Label)) {
                        ((WebControl)ctl).TabIndex = this.TabIndex;
                    }
                }
            }
        }

        private void SkipDataModificiationCheck(GridViewRow header) {
            foreach (TableCell cell in header.Cells) {
                if (cell.Controls.Count > 0 && (cell.Controls[0] is WebControl)) {
                    WebControl control = cell.Controls[0] as WebControl;
                    control.Attributes.Add("onclick", WebConstants.SKIP_DATA_MODIFICATION_CHECK_FUNC);
                }
            }
        }

        #endregion Misc
        #region Sorting

        /// <summary>
        /// Gets the sort expression.
        /// </summary>
        /// <returns></returns>
        public string GetSortExpression() {
            return m_currentSortExp;
        }

        /// <summary>
        /// Determines the index of the column being sorted. -1 will be returned
        /// if there is no column
        /// </summary>
        /// <param name="currentExp">Current sort expression</param>
        /// <returns>index of sorted column</returns>
        private int GetSortColumnAndDirection(out SortDirection direction) {

            int pos = m_currentSortExp.IndexOf(SORT_COLUMN_AND_DIRECTION_SEPARATOR);
            string firstColumnName = String.Empty;
            direction = SortDirection.Ascending;

            if (pos > 0) {
                firstColumnName = m_currentSortExp.Substring(0, pos);
                int lastPosOfFirstSortColumn =
                    m_currentSortExp.IndexOf(SORT_COLUMN_SEPARATOR) - 1;
                if (lastPosOfFirstSortColumn < 0) {
                    lastPosOfFirstSortColumn = m_currentSortExp.Length - 1;
                }
                string sortDirectionText = m_currentSortExp.Substring(pos + 1,
                    lastPosOfFirstSortColumn - pos);
                direction = sortDirectionText == ASCENDING_SORT_DIRECTION ?
                    SortDirection.Ascending : SortDirection.Descending;
            }

            for (int i = 0; i < Columns.Count; i++) {
                DataControlField field = Columns[i];
                //Get the first column from 
                //sort expression (because there is sort expression having
                //more than one column in it)
                if (field.SortExpression == firstColumnName) {
                    return i;
                }
            }

            return -1;
        }

        // This is a helper method used to add a sort direction
        // image to the header of the column being sorted.
        private void AddSortImage(GridViewRow headerRow) {
            if (!m_visibleSortImageInFirstTime) {
                return;
            }

            SortDirection direction;
            int sortColumnIndx = GetSortColumnAndDirection(out direction);
            if (sortColumnIndx != -1) {
                // Create the sorting image based on the sort direction.
                Image sortImage = new Image();
                //sortImage.Style.Add(HtmlTextWriterStyle.PaddingRight, "5px");
                if (direction == SortDirection.Ascending) {
                    sortImage.ImageUrl = AscImageURL;
                    sortImage.AlternateText = "Ascending Order";
                } else {
                    sortImage.ImageUrl = DescImageURL;
                    sortImage.AlternateText = "Descending Order";
                }

                // Add the image to the appropriate header cell.

                headerRow.Cells[sortColumnIndx].Controls.AddAt(0, sortImage);

                Literal literal = new Literal();
                literal.Text = "&nbsp;&nbsp;";
                headerRow.Cells[sortColumnIndx].Controls.AddAt(1, literal);
            }
        }

        private static string RemoveFirstSortColumn(string sortExpression) {
            int pos = sortExpression.IndexOf(SORT_COLUMN_SEPARATOR);
            if (pos > 0) {
                return sortExpression.Substring(pos + 1, sortExpression.Length - pos - 1);
            } else {
                return String.Empty;
            }
        }

        private static string RemoveLastSortColumn(string sortExpression) {
            string[] sortColumnList = sortExpression.Split(SORT_COLUMN_SEPARATOR);
            if (sortColumnList.Length == MAX_NBR_OF_COLUMNS_IN_SORT_EXP) {
                // Remove the last column from the sort expression
                return String.Join(Convert.ToString(SORT_COLUMN_SEPARATOR),
                                   sortColumnList, 0, sortColumnList.Length - 1);
            } else {
                return sortExpression;
            }
        }

        public void OnGridViewSorting(object sender, GridViewSortEventArgs e) {
            DataView dv = DataSource as DataView;
            if (dv != null && !e.Cancel) {
                DataRowView selectedRow = null;
                if (SelectedIndex > -1) {
                    selectedRow = dv[SelectedIndex];
                }

                m_visibleSortImageInFirstTime = true;
                SortDirection sortDirection;
                int sortColumnIndx = GetSortColumnAndDirection(out sortDirection);

                string newSortDirectionText;
                if (sortColumnIndx >= 0 &&
                    e.SortExpression == Columns[sortColumnIndx].SortExpression) {

                    newSortDirectionText = sortDirection == SortDirection.Ascending ?
                        DESCENDING_SORT_DIRECTION : ASCENDING_SORT_DIRECTION;
                    m_currentSortExp = RemoveFirstSortColumn(m_currentSortExp);
                } else {

                    newSortDirectionText = ASCENDING_SORT_DIRECTION;
                    m_currentSortExp = RemoveLastSortColumn(m_currentSortExp);
                }

                if (!string.IsNullOrEmpty(m_currentSortExp)) {
                    m_currentSortExp = SORT_COLUMN_SEPARATOR + m_currentSortExp;
                }

                m_currentSortExp = e.SortExpression +
                                   SORT_COLUMN_AND_DIRECTION_SEPARATOR +
                                   newSortDirectionText + m_currentSortExp;

                dv.Sort = m_currentSortExp;
                DataBind();

                if (selectedRow != null) {
                    for (int idx = 0; idx < dv.Count; idx++) {
                        if (selectedRow == dv[idx]) {
                            m_fullSelectedIndex = idx;
                            base.SelectedIndex = CalculateSelectedIndex(idx);
                            break;
                        }
                    }
                }
            }
        }

        public virtual void OnCustomGridViewSorting(object sender, GridViewSortEventArgs e) {
            OnGridViewSorting(sender, e);
        }

        #endregion Sorting
        #region Paging
        /// <summary>
        /// Highlight the current page number.
        /// </summary>
        /// <param name="pagerRow">Pager row</param>
        private static void HighLightCurrentPage(GridViewRow pagerRow) {
            TableRow row = (TableRow)pagerRow.Cells[0].Controls[0].Controls[0];
            foreach (TableCell cell in row.Cells) {
                //The current page is a label
                if (cell.Controls.Count > 0 && cell.Controls[0] is Label) {
                    Label lbl = cell.Controls[0] as Label;
                    lbl.Font.Bold = true;
                    return;
                }
            }
        }

        /// <summary>
        /// Add the open and closing brackets for the page numbers.
        /// </summary>
        /// <param name="pagerRow">Pager row</param>
        private void AddBrackets(GridViewRow pagerRow) {

            // Set style for the pager column which contains the table.
            TableCell gridCell = (TableCell)pagerRow.Cells[0];
            gridCell.CssClass = CSS_PAGER_TABLE;

            // Set style for the pager table
            Table table = (Table)gridCell.Controls[0];
            table.CellPadding = 2;

            TableRow row = (TableRow)table.Rows[0];
            if (PageSetIndex > 0) {
                //Remove the left "..."
                row.Cells.RemoveAt(0);
            }

            if (PageSetIndex < SetCount - 1) {
                row.Cells.RemoveAt(row.Cells.Count - 1);
            }

            // Left bracket
            TableCell leftBracket = new TableCell();
            leftBracket.Text = "[";
            row.Cells.AddAt(0, leftBracket);

            // Right bracket
            TableCell rightBracket = new TableCell();
            rightBracket.Text = "]";
            row.Cells.Add(rightBracket);
        }

        private TableCell CreatePagerButton(string imgURL, string cmdArgument) {

            TableCell cell = new TableCell();
            cell.CssClass = CSS_PAGER_CELL;

            // Create hyperlink to simulate the pager button.
            HyperLink lnk = new HyperLink();
            lnk.ImageUrl = imgURL;
            lnk.NavigateUrl = "javascript:" +
                Page.ClientScript.GetPostBackEventReference(this, cmdArgument);

            cell.Controls.Add(lnk);
            return cell;
        }

        private void AddNextPrevButtons(GridViewRow pagerRow) {

            TableRow row = (TableRow)pagerRow.Cells[0].Controls[0].Controls[0];

            //Previous pager button
            if (PageIndex != 0) {
                row.Cells.AddAt(0, CreatePagerButton(URL_PREV_PAGE_IMG, "Page$Prev"));
            }

            //Next pager button
            if (PageIndex != PageCount - 1) {
                row.Cells.Add(CreatePagerButton(URL_NEXT_PAGE_IMG, "Page$Next"));
            }
        }

        private void AddPageSetNextPrevButtons(GridViewRow pagerRow) {
            TableRow row = (TableRow)pagerRow.Cells[0].Controls[0].Controls[0];

            //PreviousSet pager button
            if (PageSetIndex > 0) {
                string argument = "Page$" + (PREVIOUS_PAGE_SET + 1).ToString();
                row.Cells.AddAt(0, CreatePagerButton(URL_PREV_PAGE_SET_IMG, argument));
            }

            //NextSet pager button
            if (PageSetIndex < SetCount - 1) {
                string argument = "Page$" + (NEXT_PAGE_SET + 1).ToString();
                row.Cells.Add(CreatePagerButton(URL_NEXT_PAGE_SET_IMG, argument));
            }
        }
        /// <summary>
        /// Override for case when we select one row and paging and sort 
        /// </summary>
        public override int SelectedIndex {
            set {
                if (value == -1) {
                    base.SelectedIndex = -1;
                    m_fullSelectedIndex = -1;
                } else {
                    base.SelectedIndex = value % PageSize;
                    m_fullSelectedIndex = value;
                }
            }
            get {
                return m_fullSelectedIndex;
            }
        }

        private int CalculateSelectedIndex(int newIndex) {
            int selectIndexResult = -1;
            int pageIndexHasContainRowSelected = newIndex / PageSize;
            if (newIndex != -1) {
                if (pageIndexHasContainRowSelected == PageIndex) {
                    selectIndexResult = newIndex % PageSize;
                }
            }
            return selectIndexResult;
        }


        private void OnCustomGridViewPageIndexChanging(object sender, GridViewPageEventArgs e) {
            DataView dv = DataSource as DataView;
            DataRowView selectedRow = null;


            if (dv != null) {
                if (m_fullSelectedIndex != -1) {
                    base.SelectedIndex = m_fullSelectedIndex;
                }
                if (SelectedIndex > -1) {
                    selectedRow = dv[SelectedIndex];
                }
            }
            switch (e.NewPageIndex) {
                case PREVIOUS_PAGE_SET:
                    // PreviousPageSet buttons is clicked
                    e.NewPageIndex = PageIndex - NumPagesPerSet;
                    break;
                case NEXT_PAGE_SET:
                    // NextPageSet buttons is clicked
                    e.NewPageIndex = PageIndex + NumPagesPerSet;
                    break;
                default:
                    // Do nothing
                    break;
            }
            PageIndex = e.NewPageIndex;
            DataBind();

            if (selectedRow != null) {
                for (int idx = 0; idx < dv.Count; idx++) {
                    if (selectedRow == dv[idx]) {
                        SelectedIndex = CalculateSelectedIndex(idx);
                        break;
                    }
                }
            }

        }

        #endregion Paging
        #endregion IMethods
    }
}
